<template>
  <div class="codeBox">
    <div class="toast" v-show="toastVal">{{ toastVal }}</div>

    <div class="controlBar">
      <el-switch
        v-model="themeType"
        width="60"
        inline-prompt
        style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
        active-text="白天"
        inactive-text="黑夜"
        @change="themeChange"
      />

      <div class="copy" @click="copyText">复制</div>
    </div>

    <div class="codeContainer">
      <div class="lineNumber">
        <div v-for="item in linesArray.length" :key="item">{{ item }}</div>
      </div>

      <div id="codeDisplay" ref="codeDisplay"></div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'

const props = defineProps({
  htmlStr: {
    type: String,
    readonly: true,
    required: true,
    default: '<div> 请为 CodeShower 组件htmlStr属性赋值 </div>',
  },
  border: { type: Boolean, readonly: true, default: true },
})

onMounted(() => {
  getLinesArray()
  themeChange(true)
  setBorder()
})

const themeBorderWidth = ref<'1px' | 0>('1px')
const setBorder = () => {
  if (props.border) themeBorderWidth.value = '1px'
  else themeBorderWidth.value = 0
}

// 每一行代码数组
const linesArray = ref<string[]>([])
// 每一行代码前需要添加的缩进空格
const linesSpace = ref<number[]>([])
// 代码展示的容器
const codeDisplay = ref<HTMLElement>(null)

watch(
  () => props.htmlStr,
  () => getLinesArray()
)

// 切割代码行
const getLinesArray = () => {
  linesArray.value = []
  linesSpace.value = []

  const l = props.htmlStr.trim().split('\n')
  const l2 = l.filter((item) => item.trim().length > 0)
  linesArray.value = l2.map((item) => item.trim())

  getLineSpace(linesArray.value)
  getElementArray(linesArray.value)
}

//
// 提取出每一行前面要加的空格
const getLineSpace = (lines: string[]): string[] => {
  const result: string[] = []

  let indentLevel = 0
  linesSpace.value[0] = 0
  if (lines.length === 1) return

  // 遍历每一行
  for (let i = 1; i <= lines.length; i++) {
    // 获取上一行代码标签，
    // 通过上一行代码标签结合当前标签来判断当前代码行的缩进
    const s = lines[i - 1].trim()

    // 正则表达式
    const regex1 = /</g // 匹配所有的 '<'
    const regex2 = /<\/\s*/g // 匹配所有的 '</'
    const regex3 = /<[^\/>][^>]*>/g // 匹配所有的开标签  <**>
    const regex4 = /<\/[^>]+>/g // 匹配所有的闭合标签  </**>

    const count1 = (s.match(regex1) || []).length
    const count2 = (s.match(regex2) || []).length
    const count3 = (s.match(regex3) || []).length
    const count4 = (s.match(regex4) || []).length

    if (s.startsWith('<') && s.endsWith('/>')) {
      // 上一行代码为这种 <input type="text" />
      // 当前这行代码为这种 </div>

      linesSpace.value[i] = indentLevel

      const currentLine = lines[i].trim()
      if (
        (currentLine.match(regex4) || []).length === 1 &&
        (currentLine?.match(regex3) || []).length === 0
      ) {
        indentLevel--
        linesSpace.value[i] = indentLevel
      }

      continue
    }

    if (count3 === count4 && count3 !== 0 && count4 !== 0) {
      // 上一行代码为这种 <div> <span> 2333 </span> </div>
      // 当前这行代码为这种 </div>

      linesSpace.value[i] = indentLevel

      const currentLine = lines[i].trim()
      if (
        (currentLine.match(regex4) || []).length === 1 &&
        (currentLine?.match(regex3) || []).length === 0
      ) {
        indentLevel--
        linesSpace.value[i] = indentLevel
      }

      continue
    }

    if (s.startsWith('</') && s.endsWith('>')) {
      // 上一行代码为这种  </div>
      // 当前这行代码为这种 </div>

      linesSpace.value[i] = indentLevel

      const currentLine = lines[i]?.trim()
      if (
        (currentLine?.match(regex4) || []).length === 1 &&
        (currentLine?.match(regex3) || []).length === 0
      ) {
        indentLevel--
        linesSpace.value[i] = indentLevel
      }

      continue
    }

    if (count1 === 0 && count2 === 0 && s.endsWith('"')) {
      // 上一行代码为这种 font-size: 16px"

      indentLevel--
      linesSpace.value[i] = indentLevel
      continue
    }
    if (count1 === 0 && count2 === 0 && s.endsWith("'")) {
      // 上一行代码为这种 font-size: 16px'

      indentLevel--
      linesSpace.value[i] = indentLevel
      continue
    }
    if (count1 === 0 && count2 === 0 && s.endsWith(';')) {
      // 上一行代码为这种 style="font-size: 16px;

      linesSpace.value[i] = indentLevel
      continue
    }

    if (!s.startsWith('<') && !s.endsWith('>') && count1 === 0) {
      // 上一行代码为这种 {{2333}}
      // 当前这行代码为这种 <div>

      const currentLine = lines[i].trim()
      if (
        (currentLine.match(regex3) || []).length === 1 &&
        (currentLine?.match(regex4) || []).length === 0
      ) {
        linesSpace.value[i] = indentLevel
      } else {
        indentLevel--
        linesSpace.value[i] = indentLevel
      }

      continue
    }

    if (s.startsWith('<') && !s.endsWith('>')) {
      // 上一行代码为这种 <div class="title"

      indentLevel++
      linesSpace.value[i] = indentLevel
      continue
    }
    if (!s.startsWith('<') && s.endsWith('>')) {
      // 上一行代码为这种 font-size: 16px">

      if (s.length === 1) {
        indentLevel++ // 上一行代码为这种 >
      }
      linesSpace.value[i] = indentLevel
      continue
    }

    if (s.startsWith('<') && s.endsWith('>') && count3 === 1 && count4 === 0) {
      // 上一行代码为这种 <div>
      indentLevel = linesSpace.value[i - 1] + 1
      linesSpace.value[i] = indentLevel
      continue
    }
  }

  return result
}

// 将代码行转换为元素数组
// 1. 将每一行代码切割成一个个元素
// 2. 将元素数组存入 elementArray 中
const getElementArray = (linesArr: string[]) => {
  let temp: string[][] = []
  linesArr.forEach((item) => temp.push(parseHtmlString(item)))

  createDom(temp, linesArr)

  //
  //
  // 提取元素
  function parseHtmlString(str: string): string[] {
    const result: string[] = []

    // 匹配标签和大括号内容
    const regex = /(<[^>]+>|{{[^}]+}})/g
    let lastIndex = 0

    // 逐个匹配
    let match
    while ((match = regex.exec(str)) !== null) {
      const part = match[0]
      if (match.index > lastIndex) {
        result.push(str.substring(lastIndex, match.index).trim())
      }
      lastIndex = regex.lastIndex

      if (part.startsWith('<')) {
        const parts = part.split(/(\s+|>|<)/).filter(Boolean)

        parts.forEach((part) => {
          if (part.includes('=')) {
            const [key, value] = part.split('=')
            result.push(key.trim(), '=', value.trim())
          } else if (part !== ' ' && part !== '<' && part !== '>') {
            result.push(part.trim())
          } else if (part === '<' || part === '>') {
            result.push(part)
          }
        })
      } else if (part.startsWith('{{')) {
        result.push(part.trim())
      }
    }

    // 处理匹配后剩余的文本
    if (lastIndex < str.length) {
      result.push(str.substring(lastIndex).trim())
    }

    const t = result
    if (t.length == 3 && t[0] == '<' && t[2] == '>') return [t[0] + t[1] + t[2]]
    return result
  }

  //
  //
  // 生成代码块
  function createDom(elementArray: string[][], linesArray: string[]) {
    let lineNumber = 0
    const codeBox = codeDisplay.value
    codeBox.innerHTML = ''

    elementArray.forEach((item) => {
      const parentNode = addTag(item, lineNumber)
      codeBox?.appendChild(parentNode)
      lineNumber++
    })

    function addTag(elementArr: string[], line: number) {
      const str = linesArray[lineNumber]
      const regex1 = /(['"])(.*?)\1/g
      const regex2 = /[\w-:#]+(?=\s*=\s*)/g
      const matche1 = str.match(regex1)?.join('')
      const matche2 = str.match(regex2)?.join('')

      const parentNode = document.createElement('div')
      parentNode.style.display = 'flex'
      parentNode.style.alignItems = 'center'

      if (elementArr.length === 0) return parentNode

      for (let i = 0; i < elementArr.length; i++) {
        const item = elementArr[i]
        const itemNext = elementArr[i + 1] || ''
        const itemPrev = elementArr[i - 1] || ''

        // 匹配所有英文字母数字 和 '-' 符号
        const regex: RegExp = /^[a-zA-Z0-9-]+$/
        const regCurr = regex.test(item)
        const regNext = regex.test(itemNext)
        // const regPrev = regex.test(itemPrev)

        //
        //
        let span = document.createElement('span')
        span.style.whiteSpace = 'nowrap' // 防止换行
        if (item === '=') span.style.color = '#E6A23C'
        if (matche1?.includes(item)) span.style.color = '#b88230'
        if (matche2?.includes(item)) span.style.color = '#F56C6C'
        // console.log(item)

        //
        //
        if (itemNext.startsWith(':') || itemNext.startsWith('#')) {
          span.innerText = item
          span.style.paddingRight = '10px'
          parentNode.appendChild(span)
          continue
        }

        if (
          (item.endsWith('"') && itemNext !== '>') ||
          (item.endsWith("'") && itemNext !== '>')
        ) {
          span.innerText = item
          span.style.paddingRight = '10px'
          parentNode.appendChild(span)
          continue
        }

        if (
          (item === '<' && itemNext.startsWith('/')) ||
          (item.startsWith('/') && itemNext === '>')
        ) {
          span.innerText = item
          parentNode.appendChild(span)
          continue
        }

        if (itemNext === '=' || itemPrev === '=' || item === '=') {
          span.innerText = item
          if (
            !regex.test(item.substring(0, 1)) &&
            regex.test(item.substring(item.length - 1, item.length)) &&
            itemPrev === '='
          ) {
            span.style.paddingRight = '10px'
          }
          parentNode.appendChild(span)
          continue
        }

        if (
          regex.test(item.substring(item.length - 1, item.length)) &&
          regex.test(itemNext.substring(0, 1))
        ) {
          span.innerText = item
          span.style.paddingRight = '10px'
          parentNode.appendChild(span)
          continue
        }

        if ((!regCurr && regNext) || (regCurr && !regNext)) {
          span.innerText = item
          parentNode.appendChild(span)
          continue
        }

        span.innerText = item
        span.style.paddingRight = '10px'
        parentNode.appendChild(span)
      }

      //
      // 格式化代码，
      for (let i = 0; i < linesSpace.value[line]; i++) {
        let span = document.createElement('div')

        // span.innerText = '·'
        // span.style.fontSize = '20px'
        // span.style.width = '26px'
        // span.style.color = '#C0C4CC'
        // span.style.textAlign = 'center'

        span.style.width = '0px'
        span.style.borderRight = '1px solid #cccccc'
        span.style.height = '26px'
        span.style.marginLeft = '13px'
        span.style.marginRight = '13px'
        span.style.maxWidth = '3px'
        span.style.backgroundColor = 'rgba(0,0,0,0)'

        parentNode.insertBefore(span, Array.from(parentNode.children)[0])
      }

      return parentNode
    }
  }
}

//
//
// 主题类型
const themeType = ref<boolean>(true)
// 主题颜色
const themeColor = ref<string>('')
const themeBorder = ref<string>('')
const themeButton = ref<string>('')

// 主题改变
const themeChange = (val: boolean) => {
  if (val) {
    themeColor.value = '#f8f5f5'
    themeBorder.value = '#dbc3b3'
    themeButton.value = '#777777'
  } else {
    themeColor.value = '#303133'
    themeBorder.value = '#1C1D21'
    themeButton.value = '#C0C4CC'
  }
}

//
//
// 复制文本
const toastVal = ref<string>('')
const timer = ref<any>(null)

const copyText = () => {
  navigator.clipboard
    .writeText(props.htmlStr)
    .then(() => {
      toastVal.value = '复制成功'
      clearTimeout(timer.value)
      timer.value = setTimeout(() => (toastVal.value = ''), 2000)
    })
    .catch(() => {
      toastVal.value = '复制失败'
      clearTimeout(timer.value)
      timer.value = setTimeout(() => (toastVal.value = ''), 2000)
    })
}
</script>

<style lang="less" scoped>
.codeBox {
  width: 100%;
  position: relative;
  background-color: v-bind(themeColor);
  border: v-bind(themeBorderWidth) solid v-bind(themeBorder);
  border-radius: 8px;
  transition: all 0.5s;

  > .toast {
    position: absolute;
    top: 20px;
    left: 50%;
    transform: translate(-50%, 0);
    padding: 6px 15px;
    border-radius: 3px;
    background-color: #00000099;
    color: white;
    letter-spacing: 1px;
    user-select: none;
  }

  > .controlBar {
    padding: 0 15px;
    width: calc(100% - 30px);
    display: flex;
    justify-content: flex-end;
    align-items: center;

    > .copy {
      margin-left: 16px;
      font-weight: bold;
      font-size: 14px;
      color: v-bind(themeButton);
      cursor: pointer;
      user-select: none;
    }
  }

  --lineHeight: 26px;

  > .codeContainer {
    display: flex;
    width: 100%;
    overflow: auto;

    > .lineNumber {
      padding: 0px 16px 10px 10px;
      user-select: none;

      > div {
        line-height: var(--lineHeight);
        letter-spacing: 1px;
        font-size: 14px;
        text-align: end;
        font-weight: bold;
        color: #777777;
        font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
          Microsoft YaHei, SimSun, sans-serif;
      }
    }

    > #codeDisplay {
      line-height: var(--lineHeight);
      font-size: 15px;
      font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
        Microsoft YaHei, SimSun, sans-serif;
      font-weight: bold;
      color: v-bind(themeButton);
    }
  }
}
</style>
